#include "translate.h"
#include "env.h"
#include "util.h"
#include "absyn.h"
#include "absyn.h"
#include "temp.h"
#include "frame.h"
#include "semant.h"
#include "errormsg.h"

typedef struct patchList_ *patchList;
struct patchList_
{
    Temp_label *data; patchList next;
};

struct Cx
{
    patchList trues;
    patchList falses;
    T_stm stm;
};

struct Tr_exp_
{
    enum { Tr_ex, Tr_nx, Tr_cx} kind;
    union
    {
        T_exp ex;
        T_stm nx;
        struct Cx cx;
    } u;
};

static patchList PatchList(Temp_label *data, patchList next)
{
    patchList p = checked_malloc(sizeof(*p));
    p->data = data;
    p->next = next;
    return p;
}
static Tr_level outputLevel = NULL;

void doPatch(patchList tList, Temp_label label)
{
    for(;tList;tList = tList->next)
    {
        *(tList->data) = label;
    }
};

patchList joinPatch(patchList first, patchList second)
{
    patchList ptr = first;
    if(!first)
        return second;
    for(; ptr->next; ptr = ptr->next) ;
    ptr->next = second;
    return first;
};

Tr_exp Tr_Ex(T_exp ex)
{
    Tr_exp p = checked_malloc(sizeof(*p));
    p->kind = Tr_ex;
    p->u.ex = ex;
    return p;
}

Tr_exp Tr_Nx(T_stm stm)
{
    Tr_exp p = checked_malloc(sizeof(*p));
    p->kind = Tr_nx;
    p->u.nx = stm;
    return p;
}

Tr_exp Tr_Cx(T_stm stm, patchList trues, patchList falses)
{
    Tr_exp p = checked_malloc(sizeof(*p));
    p->kind = Tr_cx;
    p->u.cx.stm = stm;
    p->u.cx.trues = trues;
    p->u.cx.falses = falses;
    return p;
}

static T_exp unEx(Tr_exp e)
{
    if (!e)
        return (T_Const(0));

    switch(e->kind)
    {
    case Tr_ex:
        return e->u.ex;
    case Tr_cx:
    {
        Temp_temp r = Temp_newtemp();
        Temp_label t = Temp_newlabel();
        Temp_label f = Temp_newlabel();

        doPatch(e->u.cx.trues, t);
        doPatch(e->u.cx.falses, f);

        return T_Eseq(T_Move(T_Temp(r), T_Const(1)),
            T_Eseq(e->u.cx.stm,
            T_Eseq(T_Label(f),
            T_Eseq(T_Move(T_Temp(r), T_Const(0)),
            T_Eseq(T_Label(t),
                   T_Temp(r))))));
    }
    case Tr_nx:
        if (e->u.nx->kind ==T_EXP)
            return e->u.nx->u.EXP;
        return T_Eseq(e->u.nx, T_Const(0));
    }
    assert(0);
    return NULL;
};

static T_stm unNx(Tr_exp e)
{
    if (!e)
        return T_Exp(T_Const(0));
    switch(e->kind)
    {
    case Tr_ex:
        return T_Exp(e->u.ex);
    case Tr_cx:
    {
        Temp_label next = Temp_newlabel();
        doPatch(e->u.cx.trues, next);
        doPatch(e->u.cx.falses, next);
        return T_Seq(e->u.cx.stm, T_Label(next));
    }
    case Tr_nx:
        return e->u.nx;
    }
    assert(0);
    return NULL;
}

static struct Cx unCx(Tr_exp e)
{
    struct Cx cx;
    switch(e->kind)
    {
    case Tr_ex:
        cx.trues = cx.falses = NULL;
        if (e->u.ex->kind == T_CONST)
        {
            T_exp ne = T_Name(NULL);
            cx.stm = T_Jump(ne,
                  Temp_LabelList(NULL,NULL));
            if (e->u.ex->u.CONST != 0)
                cx.trues = PatchList(
                    &cx.stm->u.JUMP.exp->u.NAME,
                    NULL);
            else
                cx.falses = PatchList(
                    &cx.stm->u.JUMP.exp->u.NAME,
                    NULL);
            return cx;
        }
        cx.stm = T_Exp(e->u.ex);
        return cx;
    case Tr_cx:
        return e->u.cx;
    case Tr_nx:
        assert(0); // nx shouldn't be unCx
        cx.trues = cx.falses = NULL;
        cx.stm = e->u.nx;
        return cx;
    default:
        assert(0);
        cx.trues = NULL;    // A useless operation to avoid VS2005 warning
        return cx;
    }
}

struct Tr_access_
{
    Tr_level level;
    F_access access;
    unsigned size;
};

Tr_access Tr_Access(Tr_level level, F_access access, unsigned size)
{
    Tr_access p = checked_malloc(sizeof(*p));
    p->level = level;
    p->access = access;
    if (size)
        p->size = size;
    else
        p->size = F_singleSize();
    return p;
};

struct Tr_accessList_
{
    Tr_access data;
    Tr_accessList next;
};

Tr_accessList Tr_AccessList(Tr_access data, Tr_accessList next)
{
    Tr_accessList p = checked_malloc(sizeof(*p));
    p->data = data;
    p->next = next;
    return p;
};

T_stmList stmFirst = NULL, stmLast = NULL;
void Tr_appendStm(Tr_exp exp)
{
    T_stm stm = unNx(exp);
    if (exp->kind == Tr_ex  &&  exp->u.ex->kind == T_CONST)
        return; //ignore single const statement
    if (stmLast)
    {
        stmLast->tail = T_StmList(stm, NULL);
        stmLast = stmLast->tail;
    }
    else
    {
        stmFirst = stmLast = T_StmList(stm, NULL);
    }
}

T_stmList Tr_getStmList()
{
    return stmFirst;
}

Tr_level Tr_outermost()
{
    if (outputLevel)
        return outputLevel;
    else
        return Tr_newLevel(NULL, Temp_namedlabel("_start"), NULL);
}

Tr_level Tr_newLevel(Tr_level parent, Temp_label name, U_boolList formals)
{
    F_accessList flist;
    Tr_accessList trlist1, trlist2 = NULL;
    Tr_level p = checked_malloc(sizeof(*p));
    p->parent = parent;
    p->frame = F_newFrame(name, U_BoolList(TRUE, formals));

    assert(name);

    flist = F_formals(p->frame);
    flist = flist->next;    // skip the static link parameter
    while (flist)
    {
        trlist1 = Tr_AccessList( Tr_Access(p, flist->data,0), NULL );
        if (!trlist2)
            p->accessList = trlist1;
        else
            trlist2->next = trlist1;
        trlist2 = trlist1;
        flist = flist->next;
    }

    if (parent)
        p->depth = p->parent->depth + 1;
    else
        p->depth = 0;
    return p;
}

unsigned Tr_getLevelDepth(Tr_level level)
{
    return level->depth;
}

unsigned Tr_getSize(Ty_ty type)
{
    switch(type->kind)
    {
    case Ty_record:
    {
        unsigned sum = 0;
        Ty_fieldList fields = type->u.record;
        while (fields)
        {
            sum += Tr_getSize(fields->head->ty);
            fields = fields->tail;
        }
        return sum;
    }
    case Ty_nil:
        return 0;
    case Ty_int:
        return F_singleSize();
    case Ty_string:
        assert(0);
    case Ty_array:
    {
        unsigned sum = 0;
        Ty_subList sub = type->u.array.size;
        while(sub)
        {
            sum +=sub->data;
            sub = sub->next;
        }
        return sum * Tr_getSize(type->u.array.type);
    }
    case Ty_name:
        return Tr_getSize(type->u.name.ty);
    case Ty_void:
        return 0;
    default:
        assert(0);
    }
    return 0;
}

Tr_accessList Tr_formals(Tr_level level)
{
    return level->accessList;
}

Tr_access Tr_allocLocal(Tr_level level, bool escape, unsigned size)
{
    Tr_access p = checked_malloc(sizeof(*p));
    p->level = level;
    p->access = F_allocLocal(level->frame, escape, size);
    return p;
}

T_exp traceFP(int tgtDepth, int curDepth)
{
    T_exp fpexp = NULL;
    int count = curDepth - tgtDepth;
    if (!count)
        return T_Temp(F_FP());

    while(count)
    {
        if (!fpexp)
            fpexp = T_Mem(T_Binop(T_plus,
                T_Const(F_singleSize()),
                T_Temp(F_FP())));
        else
            fpexp = T_Mem(T_Binop(T_plus,
                T_Const(F_singleSize()),
                fpexp));
        count--;
    }
    return fpexp;
}

Tr_exp Tr_simpleVar(Tr_access a, Tr_level level)
{
    T_exp fpexp = NULL;
    assert(level->depth >= a->level->depth);

    fpexp = traceFP(a->level->depth, level->depth);
    return Tr_Ex(F_Exp(a->access, fpexp));
}

// vsub: subscript of the var
// unitSize: the size of a single unit
// tsub: subscript of the type
// result = (... (vsub[0] * tsub[1] + vsub[1]) * tsub[2] ... *tsub[n] + vsub[n+1])
T_exp Tr_traceArray(Tr_exp *vsub, int unitSize, Ty_subList tsub)
{    // TODO: runtime subscript check
    int i = 0;

    Ty_subList tptr = tsub->next;
    T_exp result = unEx(vsub[i]);
    for(i=1 ; i<MAX_LINK_SIZE && vsub[i] && tptr; i++, tptr = tptr->next)
    {
        result = unEx(Tr_binOper(T_plus,
            Tr_binOper(T_mul,
                Tr_Ex(result),
                Tr_intExp(tptr->data)),
            vsub[i]));
        /*
        result = T_Binop(T_plus, 
            T_Binop(T_mul,
                result,
                T_Const(tptr->data)),
            unEx(vsub[i]));
        */
    }
    if (result->kind != T_CONST)
        result = T_Binop(T_mul, result, T_Const(unitSize));
    else
        result = T_Const(result->u.CONST * unitSize);

    return result;
}

Tr_exp Tr_varBase(Tr_access a, Tr_level level)
{
    T_exp fpexp = traceFP(a->level->depth, level->depth);

    assert(level->depth >= a->level->depth);
    
    if (a->access->u.offset != 0)
        return Tr_Ex(T_Binop(T_plus,
            fpexp,
            T_Const(a->access->u.offset)));
    else
        return Tr_Ex(fpexp);
}

Tr_exp Tr_subArrayVar(Tr_exp mainArraySub, Tr_exp *vsub, Ty_subList tsub, int singleSize)
{
    T_exp subexp = Tr_traceArray(vsub, singleSize, tsub);
    return Tr_binOper(T_plus, mainArraySub, Tr_Ex(subexp));
    /*
    return Tr_Ex(T_Binop(T_plus,
                unEx(mainArraySub),
                subexp
                ));
    */
}

Tr_exp Tr_memVar(Tr_exp varAddress)
{
    return Tr_Ex(T_Mem( unEx(varAddress) ));
}

Tr_exp Tr_monoOper(int operKind, Tr_exp exp)
{
    T_binOp op;
    T_exp add;
    T_stm result;

    switch(operKind)
    {
    case A_selfaddOp:
    {
        op = T_plus;
        add = T_Binop(op, exp->u.ex, T_Const(1));
        result = T_Move( exp->u.ex, add);
        return Tr_Nx(result);
        break;
    }
    default:
        assert(0);
    }
    return NULL;
}

Tr_exp Tr_binOper(int operKind, Tr_exp left, Tr_exp right)
{
    T_binOp op;

    // calculate const expression at compile time
    if (left->u.ex->kind == T_CONST
        && right->u.ex->kind == T_CONST)
    {
        switch(operKind)
        {
        case A_plusOp:
            return Tr_Ex(T_Const(left->u.ex->u.CONST +
                    right->u.ex->u.CONST));
        case A_minusOp:
            return Tr_Ex(T_Const(left->u.ex->u.CONST -
                    right->u.ex->u.CONST));
        case A_timesOp:
            return Tr_Ex(T_Const(left->u.ex->u.CONST *
                    right->u.ex->u.CONST));
        case A_divideOp:
            return Tr_Ex(T_Const(left->u.ex->u.CONST /
                    right->u.ex->u.CONST));
        }
    }
    if (left->u.ex->kind == T_BINOP
    && left->u.ex->u.BINOP.left->kind == T_TEMP
    && left->u.ex->u.BINOP.right->kind == T_CONST
    && right->u.ex->kind == T_CONST)
    {
        left->u.ex->u.BINOP.right->u.CONST +=
            right->u.ex->u.CONST;
        return left;
    }

    switch(operKind)
    {
    case A_plusOp:
        op = T_plus;
        break;
    case A_minusOp:
        op = T_minus;
        break;
    case A_timesOp:
        op = T_mul;
        break;
    case A_divideOp:
        op = T_div;
        break;
    case A_andOp:
        op = T_and;
        break;
    case A_orOp:
        op = T_or;
        break;
    case A_lshiftOp:
        op = T_lshift;
        break;
    case A_rshiftOp:
        op = T_rshift;
        break;
    case A_arshiftOp:
        op = T_arshift;
        break;
    case A_xorOp:
        op = T_xor;
        break;
    default:
        assert(0);
    }
    return Tr_Ex(T_Binop(op, unEx(left), unEx(right)));
}

Tr_exp Tr_condOper(int operKind, Tr_exp left, Tr_exp right)
{
    T_relOp op;
    struct Cx cx;
    switch(operKind)
    {
    case(A_eqOp):
        op = T_eq;
        break;
    case(A_neqOp):
        op = T_ne;
        break;
    case(A_ltOp):
        op = T_lt;
        break;
    case(A_leOp):
        op = T_le;
        break;
    case(A_gtOp):
        op = T_gt;
        break;
    case(A_geOp):
        op = T_ge;
        break;
    default:
        assert(0);    //invalid operation
    }

    cx.stm = T_Cjump(op, unEx(left), unEx(right), NULL, NULL);
    return Tr_Cx( cx.stm, PatchList(&cx.stm->u.CJUMP.true, NULL),
              PatchList(&cx.stm->u.CJUMP.false, NULL));
}

Tr_exp Tr_compare(int inputop, Tr_exp left, Tr_exp right)
{
    patchList trues = NULL, falses = NULL;
    T_relOp op;
    T_stm temp;
    switch(inputop)
    {
    case A_eqOp:
        op = T_eq;
        break;
    case A_neqOp:
        op = T_ne;
        break;
    case A_ltOp:
        op = T_lt;
        break;
    case A_leOp:
        op = T_le;
        break;
    case A_gtOp:
        op = T_gt;
        break;
    case A_geOp:
        op = T_ge;
        break;
    default:
        assert(0);
    }
    temp = T_Cjump(op, unEx(left), unEx(right),
            NULL, NULL);
    trues = PatchList(&temp->u.CJUMP.true, NULL);
    falses = PatchList(&temp->u.CJUMP.false, NULL);

    return Tr_Cx(temp, trues, falses);
}


Tr_exp Tr_seqExp(Tr_exp data, Tr_exp next)
{
    return Tr_Nx( T_Seq(unNx(data), unNx(next)) );
}

Tr_exp Tr_if(Tr_exp test, Tr_exp then, Tr_exp elsee)
{
    struct Cx cx = unCx(test);
    T_stm th = unNx(then);
    T_stm el = unNx(elsee);
    Temp_label t = Temp_newlabel();
    Temp_label f = Temp_newlabel();
    Temp_label end = Temp_newlabel();
    T_stm stm = T_Seq(cx.stm,
        T_Seq( T_Label(t),
        T_Seq( th,
        T_Seq( T_Jump(T_Name(end),
                Temp_LabelList(end, NULL)),
        T_Seq( T_Label(f),
        T_Seq( el,
               T_Label(end)))))));
    doPatch(cx.trues, t);
    doPatch(cx.falses, f);
    return Tr_Nx(stm);
}

Tr_exp Tr_while(Tr_exp test, Tr_exp body, Temp_label lend)
{
    struct Cx cx = unCx(test);
    T_stm stm = unNx(body);
    Temp_label ltest = Temp_newlabel();
    Temp_label lbody = Temp_newlabel();
    T_stm result = T_Seq( T_Label(ltest),
            T_Seq( cx.stm,
            T_Seq( T_Label(lbody),
            T_Seq( stm,
            T_Seq( T_Jump( T_Name(ltest),
                Temp_LabelList(ltest,NULL)),
                   T_Label(lend))))));
    doPatch(cx.falses, lend);
    doPatch(cx.trues, lbody);
    return Tr_Nx(result);
}

Tr_exp Tr_break(Temp_label lend)
{
    return Tr_Nx(T_Jump(T_Name(lend),
            Temp_LabelList(lend,NULL)));
}

Tr_exp Tr_for(Tr_exp lo, Tr_exp test, Tr_exp hi, Tr_exp body, Temp_label lend)
{    // Treat a for-exp as a while-exp
    struct Cx ctest = unCx(test);
     T_stm shi = unNx(hi);
    T_stm slo = unNx(lo);
    T_stm sbody = unNx(body);
    Temp_label ltest = Temp_newlabel();
    Temp_label lbody = Temp_newlabel();

    T_stm result = T_Seq( slo,
            T_Seq( T_Label(ltest),
            T_Seq( ctest.stm,
            T_Seq( T_Label(lbody),
            T_Seq( sbody,
            T_Seq( shi,
            T_Seq( T_Jump(T_Name(ltest), Temp_LabelList(ltest,NULL)),
                T_Label(lend))))))));
    doPatch(ctest.trues, lbody);
    doPatch(ctest.falses, lend);

    return Tr_Nx(result);
}

Tr_exp Tr_call(S_table venv, S_table tenv, Temp_label label,
            Tr_level level, A_expList args, Temp_label breakk)
{
    A_expList eptr = args;
    T_exp texp = NULL;
    T_expList texplistNew = NULL, texplistOld = NULL;
    T_expList first = NULL;
    while (args)
    {
        texp = unEx(SEM_transExp(venv, tenv, level,
                eptr->data, breakk).exp);
        texplistNew = T_ExpList(texp, NULL);
        if (!texplistOld)
            texplistOld->tail = texplistNew;
        else
            first = texplistNew;
        texplistOld = texplistNew;
        args = args->next;
    }
    // add frame pointer as a addtional parameter
    first = T_ExpList(T_Temp(F_FP()), first);

    return Tr_Ex(T_Call(T_Name(label), first));
};

Tr_exp Tr_transExp(A_exp e)
{
    Tr_exp p = checked_malloc(sizeof(*p));
    if (!e)
        assert(0);

    switch(e->kind)
    {
    case A_stringExp:
        assert(0);
    case A_intExp:
    {
        p->kind = Tr_ex;
        p->u.ex = T_Const(e->u.intt);
        break;
    }
    case A_varExp:
    case A_recordExp:
        assert(0);
    case A_callExp:

    case A_opExp:
    case A_assignExp:
    case A_whileExp:
    case A_forExp:
    case A_seqExp:
    case A_asmExp:

    case A_decExp:

    case A_ifExp:
    case A_breakExp:
        break;
    }
    return p;
}

Tr_exp Tr_nullExp()
{
    return Tr_Ex(T_Const(0));
}

Tr_exp Tr_intExp(int i)
{
    return Tr_Ex(T_Const(i));
}

Tr_exp Tr_assign(Tr_exp left, Tr_exp right)
{
    return Tr_Nx( T_Move( unEx(left),
                unEx(right)));

}

Tr_exp Tr_varDec(Tr_access var, Tr_exp init, Tr_level level)
{
    Tr_exp acc  = Tr_simpleVar(var, level);
    if (init)
        return Tr_assign(acc, init);
    else
        return Tr_nullExp();
}

Tr_exp Tr_funcDec(S_symbol name, Tr_exp body, Tr_level level)
{
    // TO BE DONE!
    assert(0);
    return NULL;
}
